using System;

namespace CC.Yacht
{
    public class XorShiftRandom : Random
    {
        #region Constructor
        public XorShiftRandom() : this(Environment.TickCount)
        {
            // Empty Constructor
        }

        public XorShiftRandom(int seed) : this((uint)Math.Abs(seed))
        {
            // Empty constructor
        }

        public XorShiftRandom(uint seed)
        {
            Initialize(seed);
        }
        #endregion

        #region Private Constants
        private const uint SeedY = 362436069;
        private const uint SeedZ = 521288629;
        private const uint SeedW = 88675123;

        private const double IntToDoubleMultiplier = 1.0 / (int.MaxValue + 1.0);
        private const double UIntToDoubleMultiplier = 1.0 / (uint.MaxValue + 1.0);
        #endregion

        #region Private Fields
        private uint _X;
        private uint _Y;
        private uint _Z;
        private uint _W;
        #endregion

        #region Public Methods
        public void Initialize(uint seed)
        {
            _X = seed;
            _Y = SeedY;
            _Z = SeedZ;
            _W = SeedW;
        }

        public override int Next()
        {
            uint t = (_X ^ (_X << 11));
            _X = _Y;
            _Y = _Z;
            _Z = _W;
            uint w = (_W = (_W ^ (_W >> 19)) ^ (t ^ (t >> 8)));

            int returnValue = (int)(w >> 1);
            returnValue = (returnValue == Int32.MaxValue) ? Next() : returnValue;

            return returnValue;
        }

        public override int Next(int maxValue)
        {
            if (maxValue < 0)
            {
                throw new ArgumentOutOfRangeException("maxValue", maxValue, "maxValue must be greater than zero.");
            }

            uint t = (_X ^ (_X << 11));
            _X = _Y;
            _Y = _Z;
            _Z = _W;
            uint w = (_W = (_W ^ (_W >> 19)) ^ (t ^ (t >> 8)));

            return (int)((double)(int)(w >> 1) * IntToDoubleMultiplier * (double)maxValue);
        }

        public override int Next(int minValue, int maxValue)
        {
            if (minValue > maxValue)
            {
                throw new ArgumentOutOfRangeException("maxValue", maxValue, "maxValue must be greater than minValue.");
            }

            int returnValue;

            uint t = (_X ^ (_X << 11));
            _X = _Y;
            _Y = _Z;
            _Z = _W;
            uint w = (_W = (_W ^ (_W >> 19)) ^ (t ^ (t >> 8)));

            int range = maxValue - minValue;
            if (range < 0)
            {
                returnValue = minValue + (int)((double)w * UIntToDoubleMultiplier * ((double)maxValue - (double)minValue));
            }
            else
            {
                returnValue = minValue + (int)((double)(int)(w >> 1) * IntToDoubleMultiplier * (double)range);
            }

            return returnValue;
        }

        public override void NextBytes(byte[] buffer)
        {
            uint x = _X;
            uint y = _Y;
            uint z = _Z;
            uint w = _W;

            int i = 0;
            uint t;

            while (i < buffer.Length - 3)
            {
                t = (x ^ (x << 11));
                x = y;
                y = z;
                z = w;
                w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));

                buffer[i++] = (byte)w;
                buffer[i++] = (byte)(w >> 8);
                buffer[i++] = (byte)(w >> 16);
                buffer[i++] = (byte)(w >> 24);
            }

            if (i < buffer.Length)
            {
                t = (x ^ (x << 11));
                x = y;
                y = z;
                z = w;
                w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));

                buffer[i++] = (byte)w;
                if (i < buffer.Length)
                {
                    buffer[i++] = (byte)(w >> 8);
                    if (i < buffer.Length)
                    {
                        buffer[i++] = (byte)(w >> 16);
                        if (i < buffer.Length)
                        {
                            buffer[i] = (byte)(w >> 24);
                        }
                    }
                }
            }

            _X = x;
            _Y = y;
            _Z = z;
            _W = w;
        }

        public override double NextDouble()
        {
            uint t = (_X ^ (_X << 11));
            _X = _Y;
            _Y = _Z;
            _Z = _W;
            uint w = (_W = (_W ^ (_W >> 19)) ^ (t ^ (t >> 8)));

            return (int)(w >> 1) * IntToDoubleMultiplier;
        }

        public double NextDouble(double maxValue)
        {
            if (maxValue < 0)
            {
                throw new ArgumentOutOfRangeException("maxValue", maxValue, "maxValue must be greater than zero.");
            }

            uint t = (_X ^ (_X << 11));
            _X = _Y;
            _Y = _Z;
            _Z = _W;
            uint w = (_W = (_W ^ (_W >> 19)) ^ (t ^ (t >> 8)));

            return (double)(int)(w >> 1) * IntToDoubleMultiplier * maxValue;
        }

        public double NextDouble(double minValue, double maxValue)
        {
            if (minValue > maxValue)
            {
                throw new ArgumentOutOfRangeException("maxValue", maxValue, "maxValue must be greater than minValue.");
            }

            double range = maxValue - minValue;

            if (range == double.PositiveInfinity)
            {
                throw new ArgumentException();
            }

            uint t = (_X ^ (_X << 11));
            _X = _Y;
            _Y = _Z;
            _Z = _W;
            uint w = (_W = (_W ^ (_W >> 19)) ^ (t ^ (t >> 8)));
            
            return minValue + (double)(int)(w >> 1) * IntToDoubleMultiplier * range;
        }

        #endregion
    }
}
